//
/*   Helper code for Matrix * Vector operation program
*    Copyright (C) 2009 Goffredo Marocchi
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <cstdio>
#include "MatTest_helper.h"
#include "cublas.h"

////////////////////////////////////////////////////
/////CUBLAS
////////////////////////////////////////////////////

/** \brief General data allocation function.
*
*	This function dynamically allocates a memory region of size data_size in device memory (GPU RAM)
*	using cublasAlloc().\n
*	This wrapper is useful to neatly check for errors.
*
*	\param d_ptr pointer to device memory, float*&.
*	\param n_elem number of elements we want to allocate, const int
*	\return void
*/
void CUBLAS_Allocate_GPU_memory (float* &d_ptr, const int n_elem) {

	cublasStatus status;

	/* Allocate device memory for the matrices */
	status = cublasAlloc(n_elem, sizeof(d_ptr[0]), (void**)&d_ptr);
	if (status != CUBLAS_STATUS_SUCCESS) {
		fprintf (stderr, "!!!! device memory allocation error\n");
		exit( EXIT_FAILURE);
	}

	return;

}

/** \brief General data allocation function.
*
*	This function frees a dynamically allocated memory region in device memory (GPU RAM)
*	using cublasFree().\n
*	This wrapper is useful to neatly check for errors.
*
*	\param d_ptr pointer to device memory, float*.
*	\return void
*/
void CUBLAS_Free_GPU_memory (float * d_ptr) {

	cublasStatus status;

	status = cublasFree(d_ptr);

	if (status != CUBLAS_STATUS_SUCCESS) {
		fprintf (stderr, "!!!! device access error (write)\n");
		exit( EXIT_FAILURE);
	}

	return;

}

/** \brief General data upload function.
*
*	This function uploads a block of host memory to device memory (main RAM --> GPU RAM).\n
*	Both memory regions must have been already allocated before this function is called.\n
*	This wrapper is useful to neatly check for errors.
*
*	\param d_ptr reference to the memory block in device memory, float*&.
*	\param h_ptr reference memory block in host memory, float*.
*	\param n_elem number of elements we want to allocate, const int
*	\return void
*/
void CUBLAS_Write_to_GPU (float * &d_ptr, float *h_ptr, const int n_elem) {

	cublasStatus status;

	/* Initialize the device matrices with the host matrices */
	status = cublasSetVector(n_elem, sizeof(h_ptr[0]), h_ptr, 1, d_ptr, 1);
	if (status != CUBLAS_STATUS_SUCCESS) {
		fprintf (stderr, "!!!! device access error (write)\n");
		exit( EXIT_FAILURE);
	}

	return;
}

/** \brief General data readback function.
*
*	This function transfers a block of device memory to host memory (GPU RAM --> main RAM).\n
*	Both memory regions must have been already allocated before this function is called.\n
*	This wrapper is useful to neatly check for errors.
*
*	\param d_ptr reference to the memory block in device memory, float*&.
*	\param h_ptr reference memory block in host memory, float*.
*	\param n_elem number of elements we want to allocate, const int
*	\return void
*/
void CUBLAS_Read_from_GPU (float *h_ptr,  float * &d_ptr, const u32 data_size) {

	cublasStatus status;

	status = cublasGetVector(data_size, sizeof(h_ptr[0]), d_ptr, 1, h_ptr, 1);
    if (status != CUBLAS_STATUS_SUCCESS) {
        fprintf (stderr, "!!!! device access error (read)\n");
        exit( EXIT_FAILURE);
    }

	return;
}

/** \brief Error checking function.
*
* Launching this function will check for the last error occurred on invocation of any CUBLAS function.
* Reading the error status via CUBLAS_check_error() resets the internal error state to CUBLAS_STATUS_SUCCESS.
*
*	\return void
*/
void CUBLAS_check_error() {

	cublasStatus status;

	status = cublasGetError();
    if (status != CUBLAS_STATUS_SUCCESS) {

    	fprintf (stderr, "!!!! kernel execution error.\n");
    	exit( EXIT_FAILURE);
    }

	return;

}

/** \brief CUBLAS initialization function.
*
* This function initializes and starts the CUBLAS library and checks for successful completion of the whole process.
* This function must be called before any other CUBLAS function is invoked.
*
*	\return void
*/
void CUBLAS_Init () {

	cublasStatus status;

	status = cublasInit();
	if (status != CUBLAS_STATUS_SUCCESS) {
		fprintf (stderr, "!!!! CUBLAS initialization error\n");
		exit( EXIT_FAILURE);
	}

	return;

}
